Lambda実行環境のTCPコネクション維持について調べてみた
CX事業本部の岩田です。
Lambdaのプログラミングモデルではデータベース接続等の処理はhandler外の初期化処理で実施することがベストプラクティスとされています。コストの高い処理は初期化処理内で実施することで、ウォームスタート時のパフォーマンスを向上させることができます。
一方で、handler内の処理が完了した後、次回のhandler呼び出しまでLambdaの実行環境は「フリーズ」することが知られています。
実際には、サービスは Lambda 関数の完了後実行コンテキストをフリーズさせ、再び Lambda 関数が呼び出された際に AWS Lambda がコンテキストを再利用する場合は、コンテキストを解凍して再利用します。
初期化処理で確立したデータベースとの接続って、Lambda実行環境がフリーズしてる間はどうやって接続が維持されるんだろう??と疑問に思ったので、パケットキャプチャを行いながら検証してみました。
環境
今回利用した環境です
VPC内のEC2上にPostgresql10のDBサーバーを構築し、Lambdaの初期処理から接続しにいきます。 Lambda実行前からEC2の上ではtcpdumpによるパケットキャプチャを流しておき、後ほどWiresharkから分析します
ソースコード
これだけです。
import psycopg2 import time import json conn = psycopg2.connect(port=5432, host='ip-172-31-41-239.ap-northeast-1.compute.internal', database="lambda_db", user="lambda_user", password="lambda") def lambda_handler(event, context): with conn.cursor() as cur: cur.execute('select now()') print(cur.fetchone()) # TODO implement return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
Lambdaの初期化処理の中でEC2上のPostgresqlに接続し、明示的なクローズ処理は特に実装していません。EC2上で
tcpdump -vvv -i eth0 -s 0 -tttt port 5432 -w /home/ec2-user/lambda.cap
を実行しながら、
- Lambdaのテストイベント実行
- コンテナが破棄されるであろう時間放ったらかす
を繰り返します。
結果
こんな結果でした。
- 9:58にLambdaからEC2(Postgresql)に接続
- 10:03にLambdaからEC2にKeep Aliveパケット送信 & EC2からACK
- 10:08にLambdaからEC2にFIN/ACK送信 & EC2もFIN/ACKで応答
- 10:08にLambdaからACKで応答
Lambda実行環境はフリーズしていると言いながら、実はTCPのKeepAliveパケットは送ってくれているようです。また、Lambda実行環境が破棄されるタイミングでも、マナー悪くブツ切りしている訳ではなくちゃんとFIN/ACKを送信してくれるようです。
接続が切れた後に再度Lambdaのテストイベントを実行し、X-Rayから確認したところInitialization
が走っていたのでLambda実行環境のライフサイクルに合わせてTCPのコネクションを切断しているという理解で良さそうです。
検証結果の別パターンです。
- 17:07にLambdaからEC2(Postgresql)に接続
- 17:12にLambdaからEC2にKeep Aliveパケット送信 & EC2からACK
- 17:17に再度LambdaからEC2にKeep Aliveパケット送信 & EC2からACK
- 17:17にLambdaからEC2にFIN/ACK送信 & EC2もFIN/ACKで応答
- 17:17にLambdaからACKで応答
最初に試した時とは違い、Keep Aliveパケットの往復が2回に増えています。 何度か試しましたが、いずれの場合もKeep Aliveパケットの往復が1回もしくは2回で接続が切れていました。
Postgresqlのパラメータtcp_keepalives_xxxxを変更した場合
さらにお試しでPostgresqlのパラメータを以下のように変更してみました
tcp_keepalives_idle = 30 # TCP_KEEPIDLE, in seconds; tcp_keepalives_interval = 5 # TCP_KEEPINTVL, in seconds; tcp_keepalives_count = 1 # TCP_KEEPCNT;
これでLambdaのテストイベント終了後、約30秒間隔でPostgresqlからLambda実行環境に対してKeep Aliveパケットが送信されることになります。Lambda実行環境はちゃんとACKを返してくれるのでしょうか??結果は以下のようになりました。
ちゃんとACKを返してくれています!! 5分に1回一瞬だけフリーズを解除してKeep Aliveパケットを送っている訳ではなく、ちゃんとKeep Aliveパケットを受信できる体制も整っているようです。
まとめ
Lambdaの初期化処理内で確立された外部コンポーネントとのTCPコネクションはLambdaの実行基盤側が良い感じに接続を維持してくれるようです。おそらくWorkerもしくはWorker上のMicroVMが処理してくれているのでしょう!
また1つLambdaの裏側に詳しくなった気がします。